/**
 * @author Cator Vee  hi@catorv.com
 */
import {mixin, forEach, map, getObject, isString} from '../utils'
import {queryString, buildUrl, urlEncode} from '../utils/url'
import {vee as v, app} from '../'
import fetch from '../fetch'
import log from './logger'
import env from './env'

let responseHandlers = {}
let id = 1

function ApiError (code, message) {
  this.isApiError = true
  this.code = code | 0
  this.message = message
}

function HttpError (response) {
  this.isHttpError = true
  this.code = response.status
  this.response = response
  this.message = response.status + ' ' + response.statusText
}

function doMethod (method, url, headers, handler) {
  return function retry (params, options = {}) {
    let origParams = {...params}
    let destUrl = url
    let currentId = id++
    let timeStart = Date.now()

    forEach(params, (value, key) => {
      let placeholder = '{' + key + '}'
      if (destUrl.indexOf(placeholder) >= 0) {
        destUrl = destUrl.replace(placeholder, urlEncode(value))
        delete params[key]
      }
    })
    options.method = method

    options.headers = mixin(options.headers, headers)
    if (app.context.token) {
      options.headers['X-Token'] = app.context.token
    }
    options.headers['X-Client-Id'] = env.clientId

    log.debug(`#${currentId}: ${method} ${destUrl}`, params, options.headers)

    if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
      switch (options.headers['Content-Type']) {
        case 'application/json':
          options.body = JSON.stringify(params)
          break
        default:
          options.body = queryString(params)
        // let data = new FormData()
        // forEach(params, (value, key) => {
        //   console.log(key, value)
        //   data.append(key, value)
        // })
        // options.body = data
        // console.log(params, data)
      }
    } else {
      destUrl = buildUrl(params, destUrl).replace(/#.*$/, '')
    }

    return fetch(destUrl, options).then(function (response) {
      const status = response.status
      if (status >= 200 && status < 300) {
        let token = response.headers.get('X-Token')
        if (token) {
          app.updateContext({token})
        }
        let contentType = response.headers.get('Content-Type')
        if (!contentType) {
          return Promise.resolve(null)
        }
        let pos = contentType.indexOf(';')
        if (pos >= 0) {
          contentType = contentType.substring(0, pos)
        }
        switch (contentType.toLowerCase()) {
          case 'application/json':
            return response.json()
          default:
            return response.text()
        }
      }
      if (status === 800) {
        return response.text().then(text => {
          let code = response.headers.get('X-Code')
          throw new ApiError(code, text)
        })
      }
      throw new HttpError(response)
    }).then(data => {
      log.info(`#${currentId} - ${Date.now() - timeStart}ms - ${destUrl}`, data)
      let fn = getObject(handler, responseHandlers)
      if (fn) {
        fn(data, handler)
      }
      fn = responseHandlers['*']
      if (fn) {
        fn(data, handler)
      }
      return Promise.resolve(data)
    }).catch(err => {
      log.warn(`#${currentId} - ${Date.now() - timeStart}ms - ${destUrl}`, err)
      let errorHandler = responseHandlers['#' + err.code]
      if (errorHandler) {
        return errorHandler(err) || Promise.reject(err)
      } else if (err.code === 1091 || err.code === 401) { // 兼容
        return app.apis.auth.login.doPost({wxOpenId: app.context.openid})
          .then(() => retry(origParams, options))
      }
      throw err
    })
  }
}

function generateMethods (api = {}, name, handler) {
  handler = (handler || '') + (name ? (handler ? '.' + name : name) : '')
  if (name && isString(api)) {
    const FORM = {'Content-Type': 'application/x-www-form-urlencoded'}
    const JSON = {'Content-Type': 'application/json'}
    return {
      doGet: doMethod('GET', api, null, handler),
      doPost: doMethod('POST', api, FORM, handler),
      doPut: doMethod('PUT', api, FORM, handler),
      doPatch: doMethod('PATCH', api, FORM, handler),
      doPostJson: doMethod('POST', api, JSON, handler),
      doPutJson: doMethod('PUT', api, JSON, handler),
      doPatchJson: doMethod('PATCH', api, JSON, handler),
      doDelete: doMethod('DELETE', api, null, handler)
    }
  }
  let result = map(api, (value, key) => generateMethods(value, key, handler))
  if (result['/']) {
    mixin(result, result['/'])
    delete result['/']
  }
  return result
}

let defaultErrorHandler = err => {
  log.error(err)
  if (err.message === '该微信号未绑定用户') {
    return
  }
  v.ui.alert(err.isApiError
    ? err.message // `${err.message} (code:${err.code})`
    : '服务器忙，请稍后再试！'
  )
}

export default {
  generate: function (config, handlers) {
    responseHandlers = handlers || {}
    return generateMethods(config)
  },
  handleError: defaultErrorHandler
}
